<!--
20240814最后修改
@author ljy
对话框公共组件，用于统一对话框的样式。本主键不对对话框的主体部分进行处理
一、使用方法
1.引入组件
import BaseDialog from '@/components/base/dialog/BaseDialog.vue';
2.调用组件
<BaseDialog ref="baseDialogRef" :dialogWidth="title" @closed="closed">
3.定义该组件对象
//定义对话框组件对象
const baseDialogRef = ref();

};
-->
<template>
  <el-form ref="formRef" :model="formObject" :inline="formInline" :rules="formRules" label-width="auto" :label-position="formLabelPosition">
    <template v-for="(item, index) in formHeader" :key="index">
      <!--如果有分割线，则显示分页线1-->
      <el-divider v-if="item.type === 'divider'" class="foot_divider">{{ item.label }}</el-divider>
      <!--如果是customTemplate，则表示完全自定义内容，与下面的custom不同，custom是局部-->
      <template v-else-if="item.type === 'customTemplate'">
        <slot :name="item.prop" v-bind:item="item" v-bind:formItem="formObject[item.prop]"></slot>
      </template>
      <!-- 说明：
       1.针对部分属性，例如实际绑定的参数是dapartmentId，但是要提交的时候要封装为department.id，为了避免验证规则针对的还是departmentId，特此修改
       :prop="`${item.propAsName ? item.propAsName : item.prop}`"
        -->
      <el-form-item
        v-else-if="item.action && item.action.length > 0 ? item.action === props.formAction : true"
        :label="item.label + ':'"
        :label-position="item.labelPosition ? item.labelPosition : formLabelPosition"
        :prop="`${item.propAsName ? item.propAsName : item.prop}`"
        :rules="item.disabled == true ? [] : item.rules"
        v-show="item.show != false && (item.action && item.action.length > 0 ? item.action === props.formAction : true)"
      >
        <!--如果是自定义插槽类型-->
        <template v-if="item.type === 'custom'">
          <slot :name="item.prop" v-bind:item="item" v-bind:formItem="formObject[item.prop]"> </slot>
        </template>
        <!--如果是普通文本，即直接显示文字-->
        <template v-else-if="item.type === 'label'">
          {{ formObject[item.prop] }}
        </template>
        <!--如果是文本域类型
        -->
        <el-input
          v-else-if="item.type === 'textarea'"
          type="textarea"
          :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
          :disabled="item.disabled"
          :autosize="item.autosize != null ? item.autosize : { minRows: 4, maxRows: 6 }"
          v-model="formObject[item.prop]"
          clearable
          :style="`width:${item.width ? item.width : ''}`"
        />
        <!--
             如果是日期类型
              新增：value-format属性，日期选择后的返回类型 x：时间戳类型
              修改时间：2023-01-16
              修改人：qzy
            -->
        <el-date-picker
          v-else-if="item.type === 'date'"
          type="date"
          v-model="formObject[item.prop]"
          :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
          :disabled="item.disabled"
          :size="dataPickerSize"
          :format="item.dateType ? item.dateType : 'YYYY-MM-DD'"
          :value-format="item.dateType ? item.dateType : 'YYYY-MM-DD'"
          :style="`width:${item.width ? item.width : ''}`"
        />
        <!--如果是swich类型-->
        <el-switch
          v-else-if="item.type === 'switch'"
          v-model="formObject[item.prop]"
          inline-prompt
          active-text="是"
          inactive-text="否"
          :readonly="item.readonly"
          :disabled="item.disabled"
          :style="`width:${item.width ? item.width : ''}`"
        />
        <!--如果是普通选择框。针对element plus2.5下拉框无法自设宽度的问题，这里增加了宽度的设计-->
        <el-select
          v-else-if="item.type === 'select' && item.list && item.list.length > 0"
          v-model="formObject[item.prop]"
          :multiple="item.config ? (item.config.multiple ? item.config.multiple : false) : false"
          :multiple-limit="item.config ? (item.config.multipleLimit ? item.config.multipleLimit : 0) : 0"
          clearable
          :placeholder="item.placeholder ? item.placeholder : item.label"
          :readonly="item.readonly"
          :disabled="item.disabled"
          :style="`width:${item.width ? item.width : '200px'}`"
        >
          <el-option v-for="(item1, index1) in item.list" :key="index1" :label="item1.label" :value="item1.value"></el-option>
        </el-select>
        <!--如果是下拉框中的数据是对象。item.config.valueKey不传值则默认是id属性-->
        <el-select
          v-else-if="item.type === 'selectObject' && item.list && item.list.length > 0 && item.config && item.config.defaultProp"
          v-model="formObject[item.propAsName ? item.propAsName : item.prop]"
          :value-key="item.config.valueKey ? item.config.valueKey : 'id'"
          :multiple="item.config.multiple ? item.config.multiple : false"
          :multiple-limit="item.config.multipleLimit ? item.config.multipleLimit : 0"
          clearable
          :placeholder="item.placeholder ? item.placeholder : item.label"
          :style="`width:${item.width ? item.width : '200px'}`"
        >
          <el-option
            v-for="(item1, index1) in item.list"
            :key="index1"
            :label="item.config.defaultProp.label ? item1[item.config.defaultProp.label] : item1['name']"
            :value="item.config.defaultProp.value ? item1[item.config.defaultProp.value] : item1['id']"
          ></el-option>
        </el-select>
        <!-- <el-input
          v-else-if="item.type === 'number'"
          v-model.number="formObject[item.prop]"
          :readonly="item.readonly"
          :disabled="item.disabled"
          :precision="2"
          :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
          clearable
          :style="`width:${item.width ? item.width : ''}`"
        /> -->
        <el-input-number
          v-else-if="item.type === 'number'"
          v-model="formObject[item.prop]"
          :precision="item.config && item.config.number && item.config.number.precision ? item.config.number.precision : 0"
          :min="item.config && item.config.number && typeof item.config.number.min !== 'undefined' ? item.config.number.min : -Infinity"
          :max="item.config && item.config.number && typeof item.config.number.max !== 'undefined' ? item.config.number.max : Infinity"
          :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
          :readonly="item.readonly"
          :disabled="item.disabled"
          controls-position="right"
          clearable
          :style="`width:${item.width ? item.width : ''}`"
        >
          <template #prefix>
            <span>{{ item.config && item.config.number && item.config.number.prefix ? item.config.number.prefix : '' }}</span>
          </template>
          <template #suffix>
            <span>{{ item.config && item.config.number && item.config.number.suffix ? item.config.number.suffix : '' }}</span>
          </template>
        </el-input-number>
        <!-- <el-input-number
          v-else-if="item.type === 'integer'"
          v-model="formObject[item.prop]"
          :precision="0"
          :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
          :readonly="item.readonly"
          :disabled="item.disabled"
          controls-position="right"
          clearable
          :style="`width:${item.width ? item.width : ''}`"
        /> -->
        <FileUploadComponent
          v-else-if="item.type === 'uploadFile'"
          v-model="formObject[item.prop]"
          :tip="item.placeholder ? item.placeholder : item.label"
          :style="`width:${item.width ? item.width : ''}`"
        />
        <!--如果是图标选择类型，element plus的图标，定制开发。注意选择框宽度可以自己调整-->
        <IconSelect
          v-else-if="item.type === 'iconSelect'"
          v-model="formObject[item.prop]"
          :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
          :readonly="item.readonly"
          :disabled="item.disabled"
          :selectWidth="item.width"
        />
        <!--如果是富文本插件-->
        <WangEditorComponent v-else-if="item.type === 'editor'" v-model="formObject[item.prop]" v-bind="item.config?.wangeditor" />
        <!--如果是图片资源选择组件-->
        <PhotoSelectInput v-else-if="item.type === 'photoSelect'" v-model="formObject[item.prop]" />
        <!--如果是物料选择组件，这里是针对物料选择使用场景非常多，特别在这里增设-->
        <MaterialSelectComponent
          v-else-if="item.type === 'material'"
          v-model="formObject[item.prop]"
          v-model:materialName="formObject[item.propLabel!]"
          :readonly="item.readonly"
          @afterClickSelectMaterialButton="clearFieldValidation('materialId')"
          @finishMaterialSelect="fieldValidation('materialId')"
        />
        <!--远程加载文本框-->
        <RemoteElSelectComponent
          v-else-if="item.type === 'selectRemote' && item.config && item.config.defaultProp"
          v-model="formObject[item.prop]"
          :url="item.url!"
          :propValue="item.config.defaultProp.value ? item.config.defaultProp.value : 'id'"
          :propLabel="item.config.defaultProp.label ? item.config.defaultProp.label : 'name'"
          :propLabelRegexType="item.config.defaultProp.labelRegexType ? item.config.defaultProp.labelRegexType : 2"
          :multiple="item.config.multiple ? item.config.multiple : false"
          :multiple-limit="item.config.multipleLimit ? item.config.multipleLimit : 0"
          :queryName="item.queryNameForUrl!"
          clearable
          :placeholder="item.placeholder ? item.placeholder : item.label"
          :readonly="item.readonly"
          :disabled="item.disabled"
          :width="item.width"
        />
        <!--如果没有type类型，默认是input类型-->
        <el-input
          v-else
          :show-password="item.type === 'password' ? true : false"
          v-model="formObject[item.prop]"
          :type="`${item.type && item.type === 'password' ? 'password' : 'text'}`"
          :readonly="item.readonly"
          :disabled="item.disabled"
          :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
          clearable
          :style="`width:${item.width ? item.width : ''}`"
        />
      </el-form-item>
    </template>
    <!--表单插槽底部，用于补充其他的插槽-->
    <slot name="form_bottom"></slot>
    <el-form-item v-show="showFormFoot">
      <el-button @click="formReset()" v-show="showFormResetButton">重置</el-button>
      <el-button type="primary" @click="formSubmit(formRef)">{{ formTitle }}</el-button>
    </el-form-item>
  </el-form>
</template>

<script lang="ts" setup>
/**
 * 特别说明
 * 1.本组件不开发表单的重置功能
 * （1）设计思路：由于本组件有特殊表单元素的插槽，因此表单对象是从父组件传入，按照vue的设计思维，重置应该在父组件完成
 * （2）技术上无法实现完美的表单重置功能。
 * 因为对话框组件重置时记录的是第一次对话框打开时的初始值，因此在先执行编辑再执行添加会出现问题。现象是添加成功并重置表单后，表单数据变为第一次打开时的数据。
 * 例如第一次打开是账户user的编辑信息，那么无论后续操作了多少次对话框，执行formRef.value!.resetFields();清空表单后，都会重置为账户user的编辑信息。
 * 因为在props无法修改form的信息，因此建议在父组件去操作手动清空数据。
 * 2.功能扩展
 * （1）因为element plus从2.5版本后下拉框的宽度均要自己设置，为了保证下拉框布局问题，特意针对每个类似下拉框的组件增加宽度属性，但这个属性将不对其他文本框生效（因为其他文本框可以自动获取宽度）
 *
 */
import { ref, computed, inject, reactive, unref, defineModel, onMounted, watch, nextTick } from 'vue';
import type { FormHeader, ResponseData } from '@/types/interface/base_interface';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules, UploadFile, UploadProps } from 'element-plus';
import IconSelect from '@/components/base/form/IconSelect.vue';
import Request from '@/api/request/request';
import { defineEmits } from 'vue';
//导入自定义富文本插件
import WangEditorComponent from '@/components/base/wangeditor/WangEditorComponent.vue';
import MaterialSelectComponent from '@/components/mes/MaterialSelectComponent.vue';
import FileUploadComponent from '@/components/base/form/FileUploadComponent.vue';
//导入图片选择组件
import PhotoSelectInput from '@/components/base/form/PhotoSelectInputComponent.vue';
import RemoteElSelectComponent from '@/components/base/form/RemoteElSelectComponent.vue';
//导入加载关联属性函数
import { getValue } from '@/api/utils/GeneralUtil';
//引入wangeditor富文本编辑器
// import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
const emit = defineEmits(['beforeFormSubmit', 'afterFormSubmit', 'afterFormSubmitSuccess', 'afterFormSubmitFailure', 'afterFormReset']);

// 接收父组件的值
interface Props {
  // formTitle?: string; //表单标题。改为使用defineModel双向绑定
  // form属性在20220430修改为可选属性，意图避免在父组件初始化时，避免DIALOG初始化该属性报UNDEFINED
  // 虽然在父组件使用诸如const form = ref<Department>({});可以解决此问题，但是在typescript的Department接口中，如果包含了必须的属性，则该表单必须加上必须属性的值
  // 例如：const form = ref<Department>({ id: 1, name: '大数据技术'})，导致父组件应用中缺乏灵活性
  // form?: Record<string, any>; // 表单对象。
  formHeader: FormHeader[]; // 表单元素集合,用于生成表单
  formInline?: Boolean; // 表单是否未行内元素，对应form的inline属性。默认不是。注意如果是行内元素，建议在每个FormHeader中增加width描述
  formAction?: string; // 表单的处理类型，主要有post、put、patch等，分别代表插入、编辑和局部编辑
  formRules?: FormRules; // 整个表单的校检规则
  autoTitle?: Boolean; //是否自动标题。即根据props中的formAction属性来判断标题是否增加新增、修改等。
  formLabelWidth?: string; // 表单宽度，对应el-form的label-width属性
  formLabelPosition?: string; // 表单的标签对齐方式，对应el-form的label-position属性（left、right、top）。
  formUrl: string; // 表单提交到服务器的地址，要求在服务器的接口地址一样，只是方式不同。
  showFormFoot?: Boolean; //是否显示表单底部，即两个默认的按钮。默认显示
  showFormResetButton?: Boolean; // 是否显示重置按钮
  clearFormValueAfterSubmitSuccess?: Boolean; //表单提交成功后是否清空值，ture表示清空，默认不清空。
  dataPickerSize?: 'default' | 'large' | 'small'; //日期输入框尺寸
}

const props = withDefaults(defineProps<Props>(), {
  // formTitle: '表单',
  // form: () => ({}), // 返回空对象
  formInline: () => Boolean(false),
  formAction: 'post',
  formLabelWidth: '100px',
  formLabelPosition: 'right',
  autoTitle: () => Boolean(true),
  showFormFoot: () => Boolean(true),
  showFormResetButton: () => Boolean(true),
  clearFormValueAfterSubmitSuccess: () => Boolean(false)
});
//下面是传递参数（非函数）的写法
//inject{
//   loadTableList:{value:loadTableList,default:null}
// }
// 接收父容器传递过来的表格刷新函数，用于刷新表格信息
//注意，使用这种方式控制台不会警告，但是下面注释掉的方式会警告
//如果传递的是参数可以用下面写法
// const loadTableList = inject('loadTableList', null);
//如果传递的是函数可以用下面写法
const loadTableList = inject<() => void>('loadTableList', () => {});
//注意，使用这种方式控制台不会警告，但是下面注释掉的方式会警告
// if (typeof inject<() => void>('loadTableList') !== 'undefined') {
//   loadTableList = inject<() => void>('loadTableList'); // 表格函数
// }
//注意：defineModel在setup就开始加载，如果获取的数据需要在父组件的setup使用异步交互来读取，则会出现错误。
//也就是说在setup中执行下述代码后就直接console.log(formObject);会打印出undefined
const formObject: Record<string, any> = defineModel({
  required: true,
  default: {}
});
//20240816因该组件要用于多个场景，暂时无法覆盖多个场景，重置后的表单初始值交给父组件处理
//针对上述问题，通过下述多行代码实现准确获取表单初始值。开始代码.--------------
// const formInitObject = ref<Record<string, any>>();
// let modelValueInited: boolean = false; //用于判断defineModel是否已经初始化完成
//如果传递进来的不是空对象，则说明这个对象不是异步交互读取的。因此可以直接获取作为初始值
// if (Object.keys(formObject.value).length > 0) {
//   modelValueInited = true;
//   formInitObject.value = JSON.parse(JSON.stringify(formObject.value)); // 拷贝对象。用于重置时恢复表单初始值
// }
//如果是异步交互获取的对象，则要通过以下方式判断
// watch(
//   () => formObject.value,
//   (newVal: any, oldVal: any) => {
//     console.log(newVal, oldVal);
//     if (modelValueInited == false) {
//       //如果父组件是异步交互获取值，则采用下面的等方式。
//       //如果是第一次初始化(即原来的值是undefined，新值不是)，表示父组件异步交互获取到值传进来
//       //第一次传入newVal就有值（defineModel赋值ref对象,Proxy），必须通过.value来判断；但是第一次的oldVal因为还没有执行defineModel，因此是undefined，用oldVal.value会报错
//       //第二次当defineModel从父组件的异步交互中获取到新值时（存在值而不是空的对象），newVal则不再用newVal.value来执行，而是newVal.属性执行；oldVal则变为上一步中newVal的值
//       //下面的判断是模拟第二次的情况，即获取到表单初值时
//       if (typeof newVal !== 'undefined' && typeof oldVal !== 'undefined') {
//         if (typeof newVal.value === 'undefined' && typeof oldVal.value === 'undefined') {
//           modelValueInited = true;
//           formInitObject.value = JSON.parse(JSON.stringify(newVal)); // 拷贝对象。用于重置时恢复表单初始值
//         }
//       }
//     }
//   },
//   { immediate: true, deep: true }
// );
//针对上述问题，通过上述多行代码实现准确获取表单初始值。结束代码--------------

//接收表单标题
const formTitle = defineModel('formTitle', {
  required: false,
  default: '表单'
});
//获取初始标题，因为title属性是双向绑定的，避免在添加、编辑的反复打开中不断重复名称
const formTitleInit = formTitle.value;
//根据action获取title标题
watch(
  () => props.formAction,
  (newVal: any, oldVal: any) => {
    if (props.autoTitle) {
      if (props.formAction === 'post') {
        formTitle.value = `${formTitleInit}添加`;
      } else if (props.formAction === 'put' || props.formAction === 'patch') {
        formTitle.value = `${formTitleInit}编辑`;
      } else if (props.formAction === 'delete') {
        formTitle.value = `${formTitleInit}删除`;
      } else {
        formTitle.value = `${formTitleInit}`;
      }
    }
  },
  { immediate: true, deep: true }
);
// 表单对象
const formRef = ref<FormInstance>(); // 定义表单的ref对象
// 关闭对话框时候
const formReset = () => {
  // 重置表单。因为resetFields只记录第一次初始化的值，因为本表单大部分情况是一个组件用于多个场景，例如添加和编辑，不能作为默认重置的表单，否则会出错。
  // formRef.value!.resetFields();
  //重置表单应该改为清除验证效果
  formRef.value!.clearValidate();
  //提供给父组件用于重置后的表单赋值
  emit('afterFormReset');
};

//恢复表单为初始化
// const initForm = () => {
//   //重新赋值（在编辑的时候）
//   formObject.value = { ...formInitObject.value };
// };
// 移除不可用的表单元素属性,在表单元素为动态显示的时候，适合清除多余的空属性
const clearObjectDisabledParam = () => {
  props.formHeader.forEach((item: FormHeader) => {
    // 遍历表单元素数组
    if (item.disabled === true) {
      // 如果某个表单元素之不可用
      delete formObject.value[item.prop]; // 移除该属性
    }
  });
};
// 提交表单函数,包含添加和编辑操作
// 如果表单为disabled则不提交
const formSubmit = async (formEl: FormInstance | undefined) => {
  // 提交表单
  if (!formEl) return;
  // 表单验证
  await formEl.validate(async (valid, fields) => {
    // 如果通过表单验证
    if (valid) {
      // 如果传入表单地址
      if (props.formUrl) {
        let resData: ResponseData;
        // 移除不可用的表单元素，避免提交多余数据
        clearObjectDisabledParam();
        //对一些表单数据进行重新封装，例如将deartmentId改为department.id
        //下面是遍历查询表单中已经赋值的所有元素
        // Object.keys(formObject.value).forEach((key: string) => {
        //   props.formHeader.forEach((item: FormHeader) => {
        //     if (key === (formHeader.propAsName ? formHeader.propAsName : formHeader.prop)) {
        //   }
        // });
        // 提交表单前要处理的钩子函数，如果要在提交服务端前处理一些数据，可以在这里处理，例如处理关联对象的值，将departmentId处理为department.id再传递
        emit('beforeFormSubmit', formObject.value);
        await nextTick();
        //判断formHeader，如果添加有文件参数，说明加入了二进制文件，则与后端的交互要进行调整
        let hasFile = false;
        //遍历表单元素
        props.formHeader.forEach((item: FormHeader) => {
          // 遍历表单元素数组
          if (item.type === 'uploadFile') {
            //如果包含文件类型
            hasFile = true;
          }
        });
        //封装上传文件、其他参数。是判断与后端交互是否使用二进制流的重要参数
        const formData = new FormData();
        //如果有二进制文件要传递
        if (hasFile) {
          // 遍历对象的属性，重新添加表单元素到FormData中
          for (const key in formObject.value) {
            if (Object.prototype.hasOwnProperty.call(formObject.value, key)) {
              formData.append(key, formObject.value[key]);
            }
          }
        }
        //执行表单提交动作
        if (props.formAction === 'post') {
          if (hasFile) {
            resData = await new Request(props.formUrl, formData, {
              headers: {
                'Content-Type': 'multipart/form-data;' // 设置请求头为multipart/form-data
              }
            }).post();
          } else {
            resData = await new Request(props.formUrl, JSON.stringify(formObject.value)).post();
          }
        } else if (props.formAction === 'put') {
          resData = await new Request(props.formUrl, JSON.stringify(formObject.value)).put();
        } else if (props.formAction === 'patch') {
          resData = await new Request(props.formUrl, JSON.stringify(formObject.value)).patch();
        } else {
          //作为扩展使用，暂时在组建中没用到此处，只是为了不报错。
          resData = await new Request(props.formUrl, JSON.stringify(formObject.value)).delete();
        }
        // 编写要在提交后操作的代码
        emit('afterFormSubmit', resData);
        //鉴于该组件要用于多个场景，无法通过这里统一设置，因此都交由父组件处理
        if (resData.success) {
          //如果提交成功
          //如果默认不清空表单数据。
          // if (!props.clearFormValueAfterSubmitSuccess) {
          //   //则在编辑成功后，让初始值等于编辑成功后的值。添加和删除默认是清空，不需要专门处理。
          //   if (props.formAction === 'put' || props.formAction === 'patch') {
          //     formInitObject.value = JSON.parse(JSON.stringify(formObject.value)); // 用于更新表单初始值,主要用于编辑上
          //   }
          // }
          //重置表单
          // reset();
          // 清空表单值
          // formRef.value!.resetFields();
          // 钩子函数，提交表单成功后的操作
          // 如果要清空值建议在这里清空
          emit('afterFormSubmitSuccess');
          // 将表格重新刷新，如果存在表格
          if (loadTableList != null) {
            loadTableList();
          }
          // if (props.formAction === 'put') {
          //   // 只有修改成功，才会关闭对话框，添加操作则保持对话框状态，以便继续添加。
          //   // baseDialogRef.value.closeDialog();
          // }
          ElMessage({
            message: `${formTitle.value}成功`,
            type: 'success'
          });
        } else {
          emit('afterFormSubmitFailure');
        }
      }
    }
  });
};

/**
 * 清除表单指定字段的验证
 * 设计与20250419，主要针对表单元素是自定义组件的情况。
 * 例如自定义组件中也有表单，当表单验证失败时，这个子表单里面的验证元素也会全部失效
 */
const clearFieldValidation = (fieldName: string) => {
  //移除表单项的校验结果。
  formRef.value!.clearValidate(fieldName);
};
/**
 * 验证表单指定字段
 * 设计与20250419，主要针对表单元素是自定义组件的情况。
 * 例如自定义组件中也有表单，当表单验证失败时，这个子表单里面的验证元素也会全部失效。使用完后，重新启用验证。
 */
const fieldValidation = (fieldName: string) => {
  //移除表单项的校验结果。
  formRef.value!.validateField(fieldName);
};
defineExpose({
  formRef, //将表单元素暴露出去供父组件，主要也是反过来供formSubmit调用
  formSubmit,
  formReset,
  clearFieldValidation,
  fieldValidation
});
</script>

<style lang="scss" scoped></style>
